#ifndef __ELEMENT_H__
#define __ELEMENT_H__

#include <iostream>
#include <valarray>
#include <functional>
#include "Point.h"


/* ------------------------------ class Element begin ------------------------------ */
template <size_t DIM>
class Element
{
protected:
	std::valarray<Point<DIM> > dofs;

public:
	Element() = default;
	virtual ~Element() = default;
	void SetNDofs(size_t n);
	size_t GetNDofs() const;
	void SetDof(size_t i, const Point<DIM>& p);
	const Point<DIM>& GetDof(size_t i) const;
	const std::valarray<Point<DIM> >& GetDofs() const;
	virtual double LambdaFunction(size_t i, const Point<DIM>& p) const = 0;
	virtual std::valarray<double> LambdaGradient(size_t i, const Point<DIM>& p) const = 0;
	virtual Point<DIM> LocalToGlobal(const Point<DIM>& p) const = 0;
	virtual double GlobalToLocalJacobiDet(const Point<DIM>& p) const = 0; 
	virtual double BasisFunction(size_t i, const Point<DIM>& p) const = 0;
	virtual std::valarray<double> BasisGradient(size_t i, const Point<DIM>& p) const = 0;
	virtual std::valarray<Point<DIM> > DofsIndex(size_t ele_index, size_t n) const = 0; 
	virtual size_t SerialNumOfIndex(double i, double j, size_t n) const = 0;
	virtual size_t GetNodeNum(size_t n) const = 0;
};

template <size_t DIM>
void Element<DIM>::SetNDofs(size_t n)
{
	dofs.resize(n);
}

template <size_t DIM>
size_t Element<DIM>::GetNDofs() const
{
	return dofs.size();
}

template <size_t DIM>
void Element<DIM>::SetDof(size_t i, const Point<DIM>& p)
{
	dofs[i] = p;
}

template <size_t DIM>
const Point<DIM>& Element<DIM>::GetDof(size_t i) const
{
	return dofs[i];
}

template <size_t DIM>
const std::valarray<Point<DIM> >& Element<DIM>::GetDofs() const
{
	return dofs;
}
/* ------------------------------ class Element end ------------------------------ */








/* ------------------------------ class TriElement begin ------------------------------ */
class TriElement : public Element<2>
{
protected:
	size_t n_dofs = 3;
	
public:
	TriElement();
	TriElement(size_t _n_dofs);
	~TriElement() = default;
	virtual double LambdaFunction(size_t i, const Point<2>& p) const;
	virtual std::valarray<double> LambdaGradient(size_t i, const Point<2>& p) const;
	virtual Point<2> LocalToGlobal(const Point<2>& p) const;
	virtual double GlobalToLocalJacobiDet(const Point<2>& p) const;
	virtual std::valarray<double> LocalToGlobalJacobiTimes(const Point<2>& p, const std::valarray<double>& v) const;
};
/* ------------------------------ class TriElement end ------------------------------ */






/* ------------------------------ class P1Element begin ------------------------------ */
class P1Element : public TriElement
{
public:
	P1Element() = default;
	~P1Element() = default;
	virtual double BasisFunction(size_t i, const Point<2>& p) const;
	virtual std::valarray<double> BasisGradient(size_t i, const Point<2>& p) const;
	virtual std::valarray<Point<2> > DofsIndex(size_t ele_index, size_t n) const; 
	virtual size_t SerialNumOfIndex(double i, double j, size_t n) const;
	virtual size_t GetNodeNum(size_t n) const;
};
/* ------------------------------ class P1Element end ------------------------------ */




/* ------------------------------ class P2Element begin ------------------------------ */
class P2Element : public TriElement
{
public:
	P2Element();
	~P2Element() = default;
	virtual double BasisFunction(size_t i, const Point<2>& p) const;
	virtual std::valarray<double> BasisGradient(size_t i, const Point<2>& p) const;
	virtual std::valarray<Point<2> > DofsIndex(size_t ele_index, size_t n) const; 
	virtual size_t SerialNumOfIndex(double i, double j, size_t n) const;
	virtual size_t GetNodeNum(size_t n) const;
};
/* ------------------------------ class P2Element end ------------------------------ */

#endif
